﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

using Effects.Effects;

namespace Effects.GUI
{
    /// <summary>
    ///  Class manages the system of ChangesManagers
    /// </summary>
    [Serializable()]
    public class SettingsManagement:ISerializable
    {
        #region changes_managers
        // managers managed by this instance

        /// <summary>
        ///  Binding between effects and managers.
        /// </summary>
        [NonSerialized()]
        private Dictionary<Effect, ChangesManager> managers;

        #endregion

        #region effect_boards
        // effect boards to record changes from.

        /// <summary>
        ///  Effect board 1.
        /// </summary>
        [NonSerialized()]
        private EffectBoard board1;

        /// <summary>
        ///  Gets or sets <see cref="EffectBoard"/> 1.
        /// </summary>
        /// <remarks>
        ///  <para>
        ///   When instance of <see cref="EffectBoard"/> is set, <see cref="SettingsManagement"/> registers handler on <see cref="E:EffectBoard.StateChanged"/> event and 
        ///   unregisters handler from the previous <see cref="EffectBoard"/>.
        ///   Property accept also null as valid value. Setting null causes that <see cref="SettingsManagement"/> is not managing changes from any <see cref="EffectBoard"/>
        ///  </para>
        /// </remarks>
        public EffectBoard Board1
        {
            get{ return board1;}
            set 
            {
                if (board1 != null)
                    board1.StateChange -= this.BoardChange;
                board1 = value;
                if (board1 != null)
                    board1.StateChange += this.BoardChange;
            }
        }
        
        /// <summary>
        ///  Effect board 2.
        /// </summary>
        [NonSerialized()]
        private EffectBoard board2;

        /// <summary>
        ///  Gets or sets <see cref="EffectBoard"/> 2.
        /// </summary>
        /// <remarks>
        ///  <para>
        ///   When instance of <see cref="EffectBoard"/> is set, <see cref="SettingsManagement"/> registers handler on <see cref="E:EffectBoard.StateChanged"/> event and 
        ///   unregisters handler from the previous <see cref="EffectBoard"/>.
        ///   Property accept also null as valid value. Setting null causes that <see cref="SettingsManagement"/> is not managing changes from any <see cref="EffectBoard"/>
        ///  </para>
        /// </remarks>
        public EffectBoard Board2
        {
            get { return board1; }
            set 
            {
                if (board2 != null)
                    board2.StateChange -= this.BoardChange;
                board2 = value;
                if(board2 != null)
                    board2.StateChange += this.BoardChange;
            }
        }

        #endregion

        #region player

        /// <summary>
        ///  Player
        /// </summary>
        [NonSerialized()]
        private AudioPlayer player;

        /// <summary>
        ///  Gets or sets the player
        /// </summary>
        public AudioPlayer Player
        {
            get { return player; }
            set
            {
                player = value;
                SetPlayer(player);
            }
        }

        #endregion

        #region effect_library
        // effect library management is working with

        /// <summary>
        ///  Library to get new effects from somewhere.
        /// </summary>
        [NonSerialized()]
        private EffectLibrary library;

        /// <summary>
        ///  Gets or sets EffectLibrary used by SettingsManagement.
        /// </summary>
        public EffectLibrary Library
        {
            get { return library; }
            set
            {
                library = value;
                foreach (KeyValuePair<Effect, ChangesManager> pair in managers)
                    pair.Value.Library = library;
            }
        }

        #endregion

        #region tech_properties

        /// <summary>
        ///  Sample rate of input data.
        /// </summary>
        private int sampleRate;

        /// <summary>
        ///  Gets  the sample rate of input data.
        /// </summary>
        public int SampleRate
        {
            get { return sampleRate; }
        }

        /// <summary>
        ///  Channels count of input data.
        /// </summary>
        private int channels;

        /// <summary>
        ///  Gets the number of channels.
        /// </summary>
        public int Channels
        {
            get { return channels; }
        }

        #endregion

        #region constructor

        /// <summary>
        ///  Create new SettingsManagement that manages given boards.
        /// </summary>
        /// <param name="board1"> <see cref="EffectBoard"/> 1 to manage  </param>
        /// <param name="board2"> <see cref="EffectBoard"/> 1 to manage </param>
        public SettingsManagement(EffectBoard board1, EffectBoard board2)
        {
            this.sampleRate = -1;
            this.channels = -1;
            
            this.board1 = board1;
            board1.StateChange += this.BoardChange;
            this.board2 = board2;
            board2.StateChange += this.BoardChange;
            managers = new Dictionary<Effect, ChangesManager>();
        }

        #endregion

        #region effect_board_change

        /// <summary>
        ///  Board change event handler.
        /// </summary>
        /// <param name="sender"> Object that has raised the event </param>
        /// <param name="args"> Event arguments </param>
        private void BoardChange(object sender, EffectBoardChangeEventArgs args)
        {
            switch (args.ChangeType)
            {
                    // new effect has been added
                case EffectBoardChangeType.effectAdded:
                    Add(args.Effect);
                    break;
                    // effect has been removed
                case EffectBoardChangeType.effectRemoved:
                    Remove(args.Effect);
                    break;
                case EffectBoardChangeType.editChanges:
                    EditChanges(args.Effect);
                    break;

            }
        }

        /// <summary>
        ///  Adding new effect to manage.
        /// </summary>
        /// <param name="e"> Effect to manage </param>
        public void Add(Effect e)
        {
            ChangesManager manager = new ChangesManager(e);
            manager.ClearLaterChanges = clearLaterChanges;
            manager.Library = library;
            AttachDelegates(manager);
            manager.Player = this.player;


            managers.Add(e, manager);
        }

        /// <summary>
        ///  Removes effect.
        /// </summary>
        /// <param name="e"> Effect to remove </param>
        public void Remove(Effect e)
        {
            ChangesManager manager = managers[e];
            manager.Stop();
            manager.Mode = ChangesManager.ManagingMode.Off;
            manager.Player = null;
            DetachDelegates(manager);
            managers.Remove(e);
        }

        /// <summary>
        ///  Forces manager of given effect to save current settings as initials
        /// </summary>
        /// <param name="e"> Effect which settings are to be saved </param>
        public void EditChanges(Effect e)
        {
            ChangesManager manager = managers[e];

            ChangesManagerVisualizer visualizer = new ChangesManagerVisualizer(manager, e.GetParameters(), sampleRate,channels);
            visualizer.ShowDialog();

            // set initial settings (they might have changed)
            manager.SetInitialSettings();
            //manager.Stop();
        }

        #endregion

        #region commands
        // Play Pause Stop Record RecordNew

        /// <summary>
        ///  Simple delegate used to call commands on all ChnagesManagers.
        /// </summary>
        /// <remarks>
        ///  <para>
        ///   Reason for this delegate is to keep things simple and set command to all ChangesManagers with one call of delegate.
        ///  </para>
        /// </remarks>
        private delegate void CommandDelegate();

        /// <summary>
        ///  Simple delegate to assing player to ChangesManagers
        /// </summary>
        /// <param name="p"> AudioPlayer to assign </param>
        private delegate void SetPlayerCommandDelegate(AudioPlayer p);

        /// <summary>
        ///  Simple delegate to force managers apply changes
        /// </summary>
        /// <param name="time"> Stop time for applying the changes </param>
        private delegate void ApplyChangesCommandDelegate(int time);


        /// <summary>
        ///  Delegete to turn on/off <see cref="ChangesManager.ClearLaterChanges"/>
        /// </summary>
        /// <param name="on"> On/Off argument: true - on, false - off </param>
        private delegate void ClearLaterChangesCommandDelegate(bool on);


        /// <summary>
        ///  Play command delegate
        /// </summary>
        private CommandDelegate playDelegate;

        /// <summary>
        ///  Play
        /// </summary>
        public void Play()
        {
            if(playDelegate != null)
                playDelegate();
        }

        /// <summary>
        ///  Pause command delegate.
        /// </summary>
        private CommandDelegate pauseDelegate;

        /// <summary>
        /// Pause.
        /// </summary>
        public void Pause()
        {
            if(pauseDelegate!=null)
                pauseDelegate();
        }

        /// <summary>
        ///  Stop command delegate.
        /// </summary>
        private CommandDelegate stopDelegate;

        /// <summary>
        ///  Stop.
        /// </summary>
        public void Stop()
        {
            if(stopDelegate!=null)
                stopDelegate();
        }

        /// <summary>
        ///  Record command delegate
        /// </summary>
        private CommandDelegate recordDelegate;

        /// <summary>
        ///  Record.
        /// </summary>
        public void Record()
        {
            if(recordDelegate!=null)
                recordDelegate();
        }

        /// <summary>
        ///  Record New command delegate.
        /// </summary>
        private CommandDelegate recordNewDelegate;

        /// <summary>
        ///  Record new.
        /// </summary>
        public void RecordNew()
        {
            if(recordNewDelegate!=null)
                recordNewDelegate();
        }

        /// <summary>
        ///  Set Mode to Off delegate
        /// </summary>
        private CommandDelegate SetOffDelegate;

        /// <summary>
        ///  Sets Mode to Off
        /// </summary>
        public void SetOff()
        {
            if(SetOffDelegate != null)
                SetOffDelegate();
        }

        /// <summary>
        ///  Set Mode to Playing delegate.
        /// </summary>
        private CommandDelegate SetPlayingDelegate;

        /// <summary>
        ///  Sets Mode to Playing
        /// </summary>
        public void SetPlaying()
        {
            if(SetPlayingDelegate != null)
                SetPlayingDelegate();
        }

        /// <summary>
        ///  Set Mode to Recording delegate
        /// </summary>
        private CommandDelegate SetRecordingDelegate;

        /// <summary>
        ///  Sets Mode to Recording
        /// </summary>
        public void SetRecording()
        {
            if(SetRecordingDelegate != null)
                SetRecordingDelegate();
        }

        /// <summary>
        ///  Set Player delegate
        /// </summary>
        private SetPlayerCommandDelegate SetPlayerDelegate;

        /// <summary>
        ///  Sets player
        /// </summary>
        /// <param name="p"> Player to set to the ChangesManager </param>
        public void SetPlayer(AudioPlayer p)
        {
            if(this.SetPlayerDelegate != null)
                SetPlayerDelegate(p);
        }

        /// <summary>
        ///  Delegate for applying changes
        /// </summary>
        private ApplyChangesCommandDelegate ApplyChangesDelegate;

        /// <summary>
        ///  Applies changes until given time.
        /// </summary>
        /// <param name="time"> Stop time for applying changes. </param>
        public void ApplyChanges(int time)
        {
            if (this.ApplyChangesDelegate != null)
                ApplyChangesDelegate(time);
        }


        /// <summary>
        ///  Delegate for turning cleare later changes mode on/off.
        /// </summary>
        private ClearLaterChangesCommandDelegate ClearLaterChangesDelegate;

        /// <summary>
        ///  Indicates ON/OFF state of clear later changes mode.
        /// </summary>
        private bool clearLaterChanges;

        /// <summary>
        ///  Turns Clear later changes mode on/off.
        /// </summary>
        /// <param name="on"> On/Off parameter. True - on, False - off </param>
        public void ClearLaterChanges(bool on)
        {
            this.clearLaterChanges = on;
            if (ClearLaterChangesDelegate != null)
                ClearLaterChangesDelegate(on);
        }

        /// <summary>
        ///  Registers given manager to all command delegates.
        /// </summary>
        /// <param name="manager"> Manager to register </param>
        private void AttachDelegates(ChangesManager manager)
        {
            this.playDelegate       += manager.Play;
            this.pauseDelegate      += manager.Pause;
            this.stopDelegate       += manager.Stop;
            this.recordDelegate     += manager.Record;
            this.recordNewDelegate  += manager.RecordNew;
            this.SetOffDelegate     += manager.SetModeOff;
            this.SetPlayingDelegate += manager.SetModePlaying;
            this.SetRecordingDelegate += manager.SetModeRecording;
            this.SetPlayerDelegate += manager.SetPlayer;
            this.ApplyChangesDelegate += manager.ApplyChanges;
            this.ClearLaterChangesDelegate += manager.SetClearLaterChanges;
        }   

        /// <summary>
        ///  Unregisters given manager from all command delegates.
        /// </summary>
        /// <param name="manager"> Manager to unregister </param>
        private void DetachDelegates(ChangesManager manager)
        {
            this.playDelegate       -= manager.Play;
            this.pauseDelegate      -= manager.Pause;
            this.stopDelegate       -= manager.Stop;
            this.recordDelegate     -= manager.Record;
            this.recordNewDelegate  -= manager.RecordNew;
            this.SetOffDelegate     -= manager.SetModeOff;
            this.SetPlayingDelegate -= manager.SetModePlaying;
            this.SetRecordingDelegate -= manager.SetModeRecording;
            this.SetPlayerDelegate -= manager.SetPlayer;
            this.ApplyChangesDelegate -= manager.ApplyChanges;
            this.ClearLaterChangesDelegate += manager.SetClearLaterChanges;
        }

        /// <summary>
        ///  Manager stops listening the changes.
        /// </summary>
        public void Die()
        {
            if (this.board1 != null)
                this.board1.StateChange -= this.BoardChange;
            if (this.board2 != null)
                this.board2.StateChange -= this.BoardChange;
        }

        #endregion
       
        #region serialization 
        // serialization stuff

        //Helpful deserialization fields.

        /// <summary>
        ///  Types of effect for board 1.
        /// </summary>
        private string[] namesToInitialize1;

        /// <summary>
        ///  Managers for board 1 
        /// </summary>
        private ChangesManager[] managersToInitialize1;

        /// <summary>
        ///  Types of effects for board 2.
        /// </summary>
        private string[] namesToInitialize2;

        /// <summary>
        ///  Managers for board 2.
        /// </summary>
        private ChangesManager[] managersToInitialize2;

        /// <summary>
        ///  Converts all managers to work with given sample rate and channels count.
        /// </summary>
        /// <param name="newSampleRate"> Desired sample rate </param>
        /// <param name="newChannels"> Desired number of channels </param>
        public void Convert(int newSampleRate, int newChannels)
        {
            if (sampleRate == -1)
            {
                sampleRate = newSampleRate;
                channels = newChannels;
                return;
            }

            foreach (Effect e in managers.Keys)
                managers[e].ConvertTo(sampleRate, channels, newSampleRate, newChannels);

            this.sampleRate = newSampleRate;
            this.channels = newChannels;
        }

        /// <summary>
        ///  Deserialization constructor.
        /// </summary>
        /// <param name="info">  </param>
        /// <param name="context"> </param>
        public SettingsManagement(SerializationInfo info, StreamingContext context)
        {

            int effects1count = info.GetInt32("EffectCount1");
            namesToInitialize1 = new string[effects1count];
            managersToInitialize1 = new ChangesManager[effects1count];
            string key1 = "Effects1_";
            for (int i = 0; i < effects1count; i++)
            {
                namesToInitialize1[i] = (string)info.GetValue(key1 + i + "Name", typeof(string));
                managersToInitialize1[i] = (ChangesManager)info.GetValue(key1 + i + "Manager", typeof(ChangesManager));
            }


            int effects2count = info.GetInt32("EffectCount2");
            namesToInitialize2 = new string[effects2count];
            managersToInitialize2 = new ChangesManager[effects2count];
            string key2 = "Effects2_";
            for (int i = 0; i < effects2count; i++)
            {
                namesToInitialize2[i] = (string)info.GetValue(key2 + i + "Name", typeof(string));
                managersToInitialize2[i] = (ChangesManager)info.GetValue(key2 + i + "Manager", typeof(ChangesManager));
            }

            // read sample rate and channels
            sampleRate = info.GetInt32("Sample rate");
            channels = info.GetInt32("Channels");
        }

        /// <summary>
        ///  Initializes the SettingsManager. This method is called after SettingsManager has been deserialized. AND MUST NOT BE CALLED ELSEWHERE.
        /// </summary>
        /// <param name="board1"> Board 1 to manage </param>
        /// <param name="board2"> Board 2 to manage </param>
        /// <param name="library"> Library of effects </param>
        public void Initialize(EffectBoard board1,EffectBoard board2, EffectLibrary library)
        {
            this.board1 = board1;
            this.board1.StateChange += this.BoardChange;
            this.board2 = board2;
            this.board2.StateChange += this.BoardChange;
            this.library = library;
            this.managers = new Dictionary<Effect, ChangesManager>();

            Effect[] effects1 = new Effect[namesToInitialize1.Length];
            for (int i = 0; i < namesToInitialize1.Length; i++)
            {
                effects1[i] = library.GetEffect(namesToInitialize1[i]);
                if (effects1[i] == null)
                    throw new EffectNotFoundException(namesToInitialize1[i]);

                managersToInitialize1[i].Library = library;

                // bind effect and manager together
                managersToInitialize1[i].Effect = effects1[i];
                managersToInitialize1[i].SetInitialSettings();

                managers.Add(effects1[i], managersToInitialize1[i]);
                AttachDelegates(managersToInitialize1[i]);
            }


            Effect[] effects2 = new Effect[namesToInitialize2.Length];
            for (int i = 0; i < namesToInitialize2.Length; i++)
            {
                effects2[i] = library.GetEffect(namesToInitialize2[i]);
                if (effects2[i] == null)
                    throw new EffectNotFoundException(namesToInitialize2[i]);
                managersToInitialize2[i].Library = library;

                // bind effect and manager together
                managersToInitialize2[i].Effect = effects2[i];
                managersToInitialize2[i].SetInitialSettings();

                managers.Add(effects2[i], managersToInitialize2[i]);
                AttachDelegates(managersToInitialize2[i]);
            }

            board1.SetEffects(effects1);
            board2.SetEffects(effects2);

        }

        /// <summary>
        ///  Serialization method
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            Effect[] effects1 = board1.GetAllEffects();
            info.AddValue("EffectCount1", effects1.Length);
            string key1 = "Effects1_";
            for (int i = 0; i < effects1.Length; i++)
            {
                info.AddValue(key1 + i + "Name", effects1[i].Name);
                info.AddValue(key1 + i + "Manager", managers[effects1[i]]);
            }


            Effect[] effects2 = board2.GetAllEffects();
            info.AddValue("EffectCount2", effects2.Length);
            string key2 = "Effects2_";
            for (int i = 0; i < effects2.Length; i++)
            {
                info.AddValue(key2 + i + "Name", effects2[i].Name);
                info.AddValue(key2 + i + "Manager", managers[effects2[i]]);
            }

            // write sample rate and channels
            info.AddValue("Sample rate", sampleRate);
            info.AddValue("Channels", channels);
        }
        #endregion
    }
}
